﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Linq;
using org.mariuszgromada.math.mxparser;

namespace Utility.DataSystem
{
	[Serializable]
	public class EffectorSettingObject : SettingObject<EffectorSetting>
	{
		[Serializable]
		public class FieldValue
		{
			public string fieldName;
			public string expression;
		}

		[Serializable]
		public class VariableParams
		{
			public ActionParam[] parameters = new ActionParam[0];
		}

		public List<string> variables = new List<string> ();
		public List<VariableParams> variableParams = new List<VariableParams> ();

		public List<string> conditions = new List<string> ();
		public List<FieldValue> fieldValues = new List<FieldValue> ();

		public override bool IsValid
		{
			get
			{
				if (variableParams.Count != variables.Count)
				{
					variableParams.Clear ();
					for (int i = 0; i < variables.Count; i++)
					{
						variableParams.Add (new VariableParams ());
					}
				}

				//variables
				foreach (var v in variables)
				{
					if (!FieldCenter.ContainsField (v))
					{
						return false;
					}
				}
				//conditions
				foreach (var c in conditions)
				{
					if (string.IsNullOrWhiteSpace (c))
					{
						return false;
					}
				}
				//functions
				foreach (var f in fieldValues)
				{
					//field
					if (!FieldCenter.ContainsField (f.fieldName))
					{
						return false;
					}
					//exp
					if (string.IsNullOrWhiteSpace (f.expression))
					{
						return false;
					}
				}
				return true;
			}
		}
	}

	public class EffectorSetting : Setting<EffectorSettingObject>
	{
		class Info
		{
			public string key;
			public object[] expressions;//obj is string or Expression
			public DataValue[] paramValues;
		}

		//argument-valueGetter
		Dictionary<Argument, Info> argumentDict = new Dictionary<Argument, Info> ();
		List<Expression> conditions = new List<Expression> ();
		Dictionary<string, Expression> functions = new Dictionary<string, Expression> ();


		public string Name { get; set; } = "Effector";
		protected override string FieldSourceName => Name;

		protected override void Init ()
		{
			base.Init ();
			//arguments
			argumentDict.Clear ();
			char varName = 'a';
			for (int i = 0; i < SettingObject.variables.Count; i++)
			{
				var field = SettingObject.variables[i];
				var param = SettingObject.variableParams?.TryGet (i)?.parameters;
				if (FieldCenter.ContainsField (field))
				{
					Argument arg = new Argument (varName.ToString (), 0);
					var info = new Info ();
					info.key = field;
					if (param != null && param.Length > 0)
					{
						info.expressions = new object[param.Length];
						info.paramValues = new DataValue[param.Length];
						for (int p = 0; p < param.Length; p++)
						{
							info.expressions[p] = param[p].isExpression ? new Expression (param[p].str, argumentDict.Keys.ToArray ()) : (object)param[p].str;
						}
					}
					argumentDict.Add (arg, info);
				}
				varName++;
				if (varName == 'e')
				{
					varName++;
				}
			}
			//expressions-condition
			conditions.Clear ();
			foreach (var str in SettingObject.conditions)
			{
				if (!string.IsNullOrWhiteSpace (str))
				{
					Expression e = new Expression (str, argumentDict.Keys.ToArray ());
					conditions.Add (e);
				}
			}
			//expression-function
			functions.Clear ();
			foreach (var field in SettingObject.fieldValues)
			{
				if (!string.IsNullOrEmpty (field.expression))
				{
					if (FieldCenter.ContainsField (field.fieldName))
					{
						Expression e = new Expression (field.expression, argumentDict.Keys.ToArray ());
						functions.Add (field.fieldName, e);
					}
				}
			}
		}

		protected override DataValue GetData (IDataFieldContext context, FieldQuery fieldQuery, out bool isInterested)
		{
			var fieldKey = fieldQuery.Setting.Key;
			//check field key
			if (!functions.ContainsKey (fieldKey))
			{
				isInterested = false;
				return default;
			}

			var msg = FieldCenter.GetDebugMsg (fieldQuery);
			isInterested = true;

			bool refreshValue = fieldQuery.RefreshValue;
			//update arguments
			foreach (var pair in argumentDict)
			{
				var expressions = pair.Value.expressions;
				var parameters = pair.Value.paramValues;
				if (expressions != null && expressions.Length > 0)
				{
					for (int x = 0; x < expressions.Length; x++)
					{
						DataValue data = default;

						var p = expressions[x];
						if (p is Expression exp)
						{
							var value_Argument = exp.calculate ();
							Debug.Assert (double.IsNaN (value_Argument) == false, $"fieldKey：{fieldKey} Effector calculate result IsNaN: Please check expression");
							data = new DataValue (value_Argument);
						}
						else if (p is string str)
						{
							data = new DataValue (str);
						}

						parameters[x] = data;
					}
				}
				var valueData = context.GetValue (pair.Value.key, refreshValue, parameters);
				if (valueData.IsNumber)
				{
					pair.Key.setArgumentValue ((double)valueData.AsNumber ());
				}
				else if (valueData.IsBool)
				{
					pair.Key.setArgumentValue (Convert.ToDouble (valueData.AsBool ()));
				}
				else if (valueData.IsEnum)
				{
					pair.Key.setArgumentValue (Convert.ToInt32 (valueData.AsEnum ()));
				}
				msg?.EffectorVariable (pair.Value.key, valueData);
			}
			//check conditions
			for (int i = 0; i < conditions.Count; i++)
			{
				bool pass = conditions[i].calculate () > 0;
				msg?.EffectorCondition (i, pass);
				if (!pass)
				{
					return default;
				}
			}
			//return value
			var e = functions[fieldKey];
			var value = e.calculate ();

			if (fieldQuery.Setting.IsNumber)
			{
				Debug.Assert (double.IsNaN (value) == false, $"fieldKey：{fieldKey} Effector calculate result IsNaN: Please check expression");

				return new DataValue (value);
			}
			else if (fieldQuery.Setting.IsBool)
			{
				return new DataValue (value > 0);
			}
			else if (fieldQuery.Setting.IsEnum)
			{
				var enumVal = (Enum)Enum.Parse (fieldQuery.Setting.Type, value.ToString ());
				return new DataValue (enumVal);
			}
			else if (fieldQuery.Setting.IsString)
			{
				return e.getExpressionString ();
			}
			else
			{
				throw new NotSupportedException ();
			}
		}

	}
}